<?php

namespace common\db;

use Yii;
use yii\db\Expression;

/**
 * QueryDistanceTrait class.
 *
 * @author emhome<emhome@163.com>
 * @since 2.0
 */
trait QueryDistanceTrait {

    /**
     * 查询geometry类型字段
     * @param string $column 字段名称
     * @return $this the query object itself
     */
    public function geometry($column) {
        /* @var $this ActiveQuery */
        /* @var $modelClass ActiveRecord */
        $modelClass = $this->modelClass;
        $model = $modelClass::instance();
        $columnType = $model->getTableSchema()->getColumn($column)->dbType;
        if ($columnType == 'geometry') {
            $alis = key($this->getTablesUsedInFrom()) . '.';
            $columnName = $alis . $column;
            $gis = new Expression("ASTEXT($columnName) $column");
            $columns = [$gis];
            if ($this->select == null) {
                array_unshift($columns, $alis . '*');
            }
            $this->addSelect($columns);
        }
        return $this;
    }

    /**
     * Build a distance Expression.
     * 
     * @param string $column 字段名称
     * @param mixed $geom POINT('','')
     * @param mixed $as 别名
     * ```
     * $geom = ['lat'=>0,'lng'=>0]
     * ```
     * or
     * ```
     * $geom = POINT('120','30')
     * ```
     * @return $this the query object itself
     */
    private function distanceExpression($column, $geom, $as = false) {
        /* @var $this ActiveQuery */
        $point = false;
        if (is_array($geom) && count($geom) == 2) {
            $point = call_user_func_array(array($this, 'buildPointExpression'), $geom);
        } elseif (is_string($geom)) {
            $point = $geom;
        }
        if ($point !== false) {
            $expression = "FLOOR(ST_DISTANCE_SPHERE($point,$column))";
            if ($as !== false) {
                $expression .= " AS $as";
            }
            return new Expression($expression);
        }
        return false;
    }

    /**
     * Build a POINT Expression.
     * 
     * @param string $lng 经度
     * @param mixed $lat 纬度
     * @return string
     */
    public function buildPointExpression($lng, $lat) {
        return 'POINT(' . $lng . ',' . $lat . ')';
    }

    /**
     * Sets the active part of the query.
     * @param array $condition 参数
     * ```
     * $condition = [
     *      'gis',
     *      [$lng, $lat],
     *      $vls,
     * ];
     * ```
     * @return $this the query object itself
     */
    public function andWhereDistance($condition) {
        /* @var $this ActiveQuery */
        if (is_array($condition) || count($condition) == 3) {
            list($column, $geom, $value) = $condition;
            if (strpos($column, '.') === false) {
                $column = key($this->getTablesUsedInFrom()) . '.' . $column;
            }
            $distance = $this->distanceExpression($column, $geom, false);
            if ($distance !== false) {
                $this->andWhere(['<', $distance, $value]);
            }
        }
        return $this;
    }

    /**
     * Sets the active part of the query.
     * @param array $params 参数
     * ```
     * ['lat'=>0,'lng'=>0]
     * ```
     * @return $this the query object itself
     */
    public function andHavingDistance($condition, $alias = 'distance', $sort = SORT_ASC) {
        /* @var $this ActiveQuery */
        if (is_array($condition) || count($condition) == 3) {
            $tableAlias = key($this->getTablesUsedInFrom()) . '.';
            list($column, $geom, $value) = $condition;
            if (strpos($column, '.') === false) {
                $column = $tableAlias . $column;
            }
            $distance = $this->distanceExpression($column, $geom, $alias);
            if ($distance !== false) {
                $columns = [$distance];
                if ($this->select == null) {
                    array_unshift($columns, $tableAlias . '*');
                }
                $this->addSelect($columns);
                $this->andHaving(['<', $alias, $value]);
                $this->addOrderBy([$alias => $sort]);
            }
        }
        return $this;
    }

    /**
     * Sets the active part of the query.
     * @param array $condition 排序
     * ```
     * ['lat'=>0,'lng'=>0]
     * ```
     * @return $this the query object itself
     */
    public function andOrderByDistance($column, $location, $value = false, $sort = SORT_ASC) {
        /* @var $this ActiveQuery */
        if ($value !== false) {
            return $this->andHavingDistance([$column, $location, $value]);
        }
        $tableAlias = key($this->getTablesUsedInFrom()) . '.';
        if (strpos($column, '.') === false) {
            $column = $tableAlias . $column;
        }
        $distance = $this->distanceExpression($column, $location);
        if ($distance !== false) {
            $this->addOrderBy([$distance->__toString() => $sort]);
        }
        return $this;
    }

}
